package com.ruoyi.business.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ruoyi.bizsys.domain.*;
import com.ruoyi.bizsys.dto.PayTrans;
import com.ruoyi.bizsys.enums.RealNameStatusEnum;
import com.ruoyi.bizsys.mapper.RealNameAuthMapper;
import com.ruoyi.bizsys.service.ICustBvnMatchService;
import com.ruoyi.bizsys.service.IExtResolveAccountService;
import com.ruoyi.bizsys.service.IExtTryQueueService;
import com.ruoyi.bizsys.service.ILoanOrderService;
import com.ruoyi.bizsys.utils.BizConstants;
import com.ruoyi.business.common.AsyncService;
import com.ruoyi.business.component.MqProduce;
import com.ruoyi.business.component.mqconsumer.LoanResultConsume;
import com.ruoyi.business.domain.WalletOrderReq;
import com.ruoyi.business.domain.WithholdTrans;
import com.ruoyi.business.service.ILoanService;
import com.ruoyi.business.service.IPayTransService;
import com.ruoyi.business.service.IWalletService;
import com.ruoyi.common.constant.WalletConstants;
import com.ruoyi.common.enums.RspEnmu;
import com.ruoyi.common.exception.BusinessException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.Func;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.business.CheckAccountNameUtil;
import com.ruoyi.extdata.common.config.PayStackConfig;
import com.ruoyi.extdata.service.IExternalApiService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * @Author: Jamnsin
 * @Date: 2021/3/17 5:56 下午
 */
@Service
@Slf4j
@AllArgsConstructor
public class PayTransServiceImpl extends BaseService implements IPayTransService {

    private PayStackConfig payStackConfig;

    private IExternalApiService externalApiService;

    private ICustBvnMatchService custBvnMatchService;

    private IExtResolveAccountService resolveAccountService;

    private IExtTryQueueService tryQueueService;

    ILoanOrderService loanOrderService;

    private IWalletService walletService;



    @Override
    public void payTrans(PayTrans payTrans) {

        RealNameAuth realNameAuth = getRealNameAuth(payTrans.getUserId());

        //检查Account  超时等重试
        payTryFunc(e -> checkAccountNumber(payTrans.getAccountNumber(), payTrans.getBankCode()), payTrans, "Resolve-Account-Number");

        //检查余额
        payTryFunc(e -> checkPayAccountBalance(payTrans.getLoanAmt()), payTrans, "Check-Balance");
        //创建转账人 收款码
        String recipientCode = payTryFunc(e ->
                createTransferRecipient(payTrans.getAccountNumber(), payTrans.getBankCode(), realNameAuth), payTrans, "Create-Transfer-Recipient").toString();

        //放款
        payTryFunc(e -> initiateTransfer(payTrans.getLoanAmt(), payTrans.getPayTransSerialNo(), recipientCode), payTrans, "Initiate-Transfer");
    }

    private Object payTryFunc(Function function, PayTrans payTrans, String apiName) {
        int reTryCount = 3;
        while (reTryCount > 0) {
            try {
                log.debug("{} 接口第 {} 次调用", apiName, reTryCount);
                return function.apply(null);
            } catch (BusinessException e) {
                log.error("{} 接口第 {} 次调用异常，code = {}, msg = {}, details = {}", apiName, reTryCount, e.getCode(), e.getMessage(), e.getDetails());

                if (!StringUtils.equalsAny(e.getMessage(), RspEnmu.NETWORK_BUSY.getMessage(), RspEnmu.EXT_API_ERR.getMessage())) {
                    //如果存在定时任务 且 返回false 这种要+1
                    tryQueueService.isExistPlusOne(payTrans.getPayTransSerialNo(), apiName);
                    throw e;
                }
                //如果超时->重试三次  第三次->加入重试队列
                if (e.getMessage().equals(RspEnmu.NETWORK_BUSY.getMessage())) {
                    reTryCount--;
                }
                //如果其他错误 或者 reentryNum = 0 -> 加入重试队列
                if (reTryCount == 0 || e.getMessage().equals(RspEnmu.EXT_API_ERR.getMessage())) {
                    String actionType = getActionType(payTrans.getPayTransSerialNo());
                    if (StringUtils.equalsAny(actionType, BizConstants.WITHDRAW_ACTION_TYPE_FLAG, BizConstants.REFUND_ACTION_TYPE_FLAG)) {
                        String walletType = null;
                        if (BizConstants.WITHDRAW_ACTION_TYPE_FLAG.equals(actionType)) {
                            walletType = WalletConstants.WALLET_TYPE_BALANCE_1;
                        } else {
                            walletType = WalletConstants.WALLET_TYPE_BONUS_2;
                        }
                        WalletOrderReq walletOrderReq = WalletOrderReq.builder().walletType(walletType).orderType(WalletConstants.ORDER_TYPE_WITHDRAW).orderSerialNo(payTrans.getPayTransSerialNo()).custNo(payTrans.getCustNo()).build();
                        walletService.updateWalletOrder(walletOrderReq);
                    } else {
                        //加入重试队列
                        tryQueueService.appendTransferQueue(payTrans, apiName);
                    }

                    throw e;
                }
            }
        }
        return null;
    }

    private String getActionType(String reference) {
        if (!reference.contains(BizConstants.SERIALNO_SEPARATOR_FLAG)) {
            return BizConstants.LOAN_ACTION_TYPE_FLAG;
        } else {
            return reference.split(BizConstants.SERIALNO_SEPARATOR_FLAG)[1];
        }
    }

    public void tryFunc(Consumer consumer) {
        int reTryCount = 3;
        while (reTryCount > 0) {
            try {
                consumer.accept(null);
                break;
            } catch (BusinessException e) {
                reTryCount--;
                if (reTryCount <= 0 || !RspEnmu.NETWORK_BUSY.getMessage().equals(e.getMessage())) {
                    throw e;
                }
            }
        }
    }

    @Override
    public JSONObject verifyTransfer(String payTransSerialNo) {
        return externalApiService.invokePayStackApi("Verify-Transfer", payStackConfig.getVerifyTransfer().replace("REFERENCE", payTransSerialNo), "GET", null, null);
    }

    @Override
    public JSONObject verifyTransaction(String reference, String apiName) {
        return externalApiService.invokePayStackApi(apiName, payStackConfig.getVerifyTransaction().replace("REFERENCE", reference), "GET", null, null);
    }



    @Override
    public JSONObject withholdTrans(WithholdTrans withholdTrans) {

        JSONObject reqParam = new JSONObject();
        reqParam.put("email", withholdTrans.getEmail());
        reqParam.put("authorization_code", withholdTrans.getAuthorizationCode());
        reqParam.put("amount", withholdTrans.getWithholdAmt().multiply(new BigDecimal(100)));
        reqParam.put("reference", withholdTrans.getPayTransSerialNo());

        JSONObject resJson = externalApiService.invokePayStackApi("Charge-Authorization", payStackConfig.getChargeAuthorization(), "POST", reqParam, null);
        return resJson;
    }

    @Override
    public boolean isPayAccountBalanceSufficient(BigDecimal loanAmt) {
        JSONObject resJson = externalApiService.invokePayStackApi("Check-Balance", payStackConfig.getCheckBalance(), "GET", null, null);
        JSONArray balanceArray = resJson.getJSONArray("data");
        if (balanceArray.size() <= 0) {
            return true;
        }
        AtomicReference<BigDecimal> payAccountBalance = new AtomicReference<>();
        balanceArray.stream().forEach(p -> {
            JSONObject json = (JSONObject) p;
            if (StringUtils.equals(json.getString("currency"), "NGN")) {
                payAccountBalance.set(json.getBigDecimal("balance"));
            }
        });
        if (payAccountBalance.get() == null || loanAmt.compareTo(payAccountBalance.get()) == 1) {
            return true;
        }
        return false;
    }

    @Override
    public CustBvnMatch verifyBVNMatch(String accountNumber, String bvnNumber, String bankCode, String userId) {

        //bvn+accountNumber 完全匹配则直接获取结果
        CustBvnMatch custBvnMatcQ = new CustBvnMatch();
        custBvnMatcQ.setBvn(bvnNumber);
        custBvnMatcQ.setReqAccountNumber(accountNumber);
        List<CustBvnMatch> custBvnMatches = custBvnMatchService.selectCustBvnMatchList(custBvnMatcQ);
        if (custBvnMatches.size() > 0) {
            return custBvnMatches.get(0);
        }
        JSONObject params = new JSONObject();
        params.put("account_number", accountNumber);
        params.put("bank_code", bankCode);
        params.put("bvn", bvnNumber);
        CustBvnMatch custBvnMatch;
        JSONObject post = externalApiService.invokePayStackApi("Verify-BVN-Match", payStackConfig.getVerifyBvnMatch(), "POST", params, userId);
        custBvnMatch = JSON.parseObject(post.toJSONString(), CustBvnMatch.class);
        custBvnMatch.setCustNo(getCustInfoByUserId(userId).getCustNo());
        custBvnMatch.setApiName("Verify-BVN-Match");
        custBvnMatch.setReqAccountNumber(accountNumber);
        custBvnMatchService.insertCustBvnMatch(custBvnMatch);
        return custBvnMatch;
    }

    @Override
    public String checkAccountNumber(String accountNumber, String bankCode) {
        //查询本地
        QueryWrapper<ExtResolveAccount> eq = new QueryWrapper<ExtResolveAccount>().eq("account_number", accountNumber).eq("bank_code", bankCode).select("account_name").last("limit 1");
        ExtResolveAccount extResolveAccount = resolveAccountService.getOne(eq);

        if (Func.isNotEmpty(extResolveAccount)) {
            //历史可能存在name有逗号的
            return CheckAccountNameUtil.commaConvertSpace(extResolveAccount.getAccountName());
        }
        String resolveAccountNumberUrl = payStackConfig.getResolveAccountNumber().replace("ACCOUNT_NUMBER", accountNumber).replace("BANK_CODE", bankCode);
        JSONObject resoveAccountRes = new JSONObject();
        try {
            resoveAccountRes = externalApiService.invokePayStackApi("Resolve-Account-Number", resolveAccountNumberUrl, "GET", null, "111");
        } catch (BusinessException e) {
            if (e.getCode().equals(RspEnmu.EXTERNAL_API_RSP_FALSE.getCode()))
                throw new BusinessException(e.getDetails(), RspEnmu.ACCOUNT_NUMBER_NOT_EXISTS);
        }
        //逗号转空格
        String accountName = CheckAccountNameUtil.commaConvertSpace(resoveAccountRes.getString("account_name"));
        resolveAccountService.insert(bankCode, accountNumber, accountName);
        return accountName;
    }

    private boolean checkAccountName(String accountName, RealNameAuth realNameAuth) {
        if (realNameAuth == null) {
            realNameAuth = getRealNameAuth();
        }

        String firstNameDes = null;
        String lastNameDes = null;
        if (StringUtils.isNotBlank(accountName)) {
            String[] accountNames = accountName.toUpperCase().split(" ");
            if (accountNames.length == 3) {
                firstNameDes = accountNames[0];
                lastNameDes = accountNames[2];
            } else {
                firstNameDes = accountNames[0];
                lastNameDes = accountNames[1];
            }
        }

        String firstNameSrc = realNameAuth.getFirstName().toUpperCase();
        String lastNameSrc = realNameAuth.getLastName().toUpperCase();

        boolean flag = (StringUtils.equals(firstNameSrc, firstNameDes) || StringUtils.equals(firstNameSrc, lastNameDes))
                && (StringUtils.equals(lastNameSrc, lastNameDes) || StringUtils.equals(lastNameSrc, firstNameDes));

        return flag;
    }

    @Override
    public JSONObject initiateTransfer(BigDecimal loanAmt, String payTransSerialNo, String recipientCode) {
        JSONObject initTransReq = new JSONObject();
        initTransReq.put("source", "balance");
        initTransReq.put("reason", "User withdrawal");
        initTransReq.put("amount", loanAmt.multiply(new BigDecimal(100)));
        initTransReq.put("reference", payTransSerialNo);
        initTransReq.put("recipient", recipientCode);

        JSONObject initTransRes = externalApiService.invokePayStackApi("Initiate-Transfer", payStackConfig.getInitiateTransfer(), "POST", initTransReq, null);
        return initTransRes;
    }

    private String createTransferRecipient(String accountNumber, String bankCode, RealNameAuth realNameAuth) {
        // type=nuban&name=Zombie&description=Zombier&account_number=01000000010&bank_code=044&currency=NGN
        String custName = realNameAuth.getFirstName() + " " + realNameAuth.getMiddleName() + " " + realNameAuth.getLastName();
        JSONObject reqParam = new JSONObject();
        reqParam.put("type", "nuban");
        reqParam.put("name", custName);
        reqParam.put("description", custName);
        reqParam.put("account_number", accountNumber);
        reqParam.put("bank_code", bankCode);
        reqParam.put("currency", "NGN");

        JSONObject resData;
        try {
            resData = externalApiService.invokePayStackApi("Create-Transfer-Recipient", payStackConfig.getCreateTransferRecipient(), "POST", reqParam, null);
        } catch (Exception e) {
            throw e;
        }
        return resData.getString("recipient_code");
    }

    private boolean checkPayAccountBalance(BigDecimal loanAmt) {
        if (!isPayAccountBalanceSufficient(loanAmt)) return true;
        throw new BusinessException(RspEnmu.INSUFFICIENT_ACCOUNT_BALANCE);
    }

}
